#!/usr/bin/env python

import os.path

class ImportInfo(object):
  def __init__(self, filename, module, fromPkg=''):
    self._filename = filename
    self._module = module
    self._fromPkg = fromPkg
    self._lineNo = -1

  def getFilename(self):
    return self._filename

  def getModuleName(self):
    return self._module

  def getPackageName(self):
    return self._fromPkg

  def getLineNo(self):
    return self._lineNo
  def setLineNo(self, lineNo):
    self._lineNo = lineNo

class ClassInfo(object):
  def __init__(self, filename, name, base = '', indentLevel = 0):
    self._filename = filename
    self._name = name
    self._base = base
    self._indentLevel = indentLevel
    self._methods = {}
    self._lineNo = -1

  def getFilename(self):
    return self._filename

  def getName(self):
    return self._name

  def getBase(self):
    return self._base

  def getIndentLevel(self):
    return self._indentLevel

  def getMethods(self):
    return self._methods

  def getLineNo(self):
    return self._lineNo
  def setLineNo(self, lineNo):
    self._lineNo = lineNo

  def appendMethod(self, name, methodInfo):
    if self._methods.has_key(name):
      raise Exception('Special name of method is existed. Name: %s' % name)
    self._methods[name] = methodInfo

  def removeMethod(self, name):
    if self._methods.has_key(name):
      del self._methods[name]

class MethodInfo(object):  
  def __init__(self, filename, name, indentLevel = 0):
    self._filename = filename
    self._name = name
    self._indentLevel = indentLevel
    self._params = []
    self._lineNo = -1

  def getFilename(self):
    return self._filename

  def getName(self):
    return self._name

  def getIndentLevel(self):
    return self._indentLevel

  def getParams(self):
    return self._params

  def getLineNo(self):
    return self._lineNo
  def setLineNo(self, lineNo):
    self._lineNo = lineNo

  def appendParam(self, param):
    self._params.append(param)

  def removeParamAt(self, index):
    del self._params[index]

class PySourceParser:  
  def __init__(self, tabSize = 2, tabAsSpace = True):
    self._keywords = {'import ':self.parseImport,
                      'from ':self.parseFrom,
                      'class ':self.parseClass,
                      'def ':self.parseMethod}
    self._tabSize = tabSize
    self._tabAsSpace = tabAsSpace
    self._imports = []
    self._classes = {}
    self._globalVariables = {}
    self._globalMethods = {}
    self._stripChars = ' \t\r\n'
    self._curFile = ''
    self._curClass = ''
    self._curMethod = ''
    self._curLineNo = -1

  def getImports(self):
    return self._imports

  def getClasses(self):
    return self._classes

  def getGlobalVariables(self):
    return self._globalVariables.values()

  def getGlobalMethods(self):
    return self._globalMethods

  def parse(self, filename):
    if not os.path.exists(filename):
      raise Exception('File %s could not be found!' % filename)

    self._curFile = filename
    fs = open(filename, 'r')
    self._curLineNo = -1

    while True:
      try:
        line, lineNo = self.readLine(fs)
        
        try:
          indentLevel = self.getIndentLevel(line)
        except:
          raise Exception('Invalid indent space: %s at line %d' % (line, lineNo))

        stripedLine = line.strip(self._stripChars)
        self.parseLine(stripedLine, lineNo, fs, indentLevel)
      except StopIteration, err:
        break

  def readLine(self, fs):
    line = fs.next()
    self._curLineNo += 1
    
    stripedLine = line.strip(self._stripChars)
    
    if stripedLine == '' or stripedLine.find('#') == 0:
      return self.readLine(fs)

    lineNo = self._curLineNo
    while len(stripedLine) > 0 and (stripedLine[len(stripedLine) - 1] == ',' or stripedLine[len(stripedLine) - 1] == '\\'):
      line = line.rstrip(self._stripChars) + fs.next().strip(self._stripChars)
      stripedLine = line.strip(self._stripChars)
      self._curLineNo += 1

    return line, lineNo

  def parseLine(self, line, lineNo, fs, indentLvl):
    for keyword, processer in self._keywords.items():
      if line.find(keyword) == 0:
        re = processer(line, lineNo, fs, indentLvl)
        if re != None:
          self.parseLine(re[0], re[1], re[2], re[3])
        break

  def getIndentLevel(self, line):
    if line is None or line == '':
      return 0

    if self._tabAsSpace:
      spcCnt = 0;
      while True:
        if line[spcCnt] == ' ':
          spcCnt += 1
        else:
          break
      
      if spcCnt % self._tabSize != 0:
        print 'space count:%d, tab size:%d' % (spcCnt, self._tabSize)
        raise Exception('Invalid indent space: %d %s in %s' % (spcCnt, os.linesep, line))
      return spcCnt / self._tabSize
    else:
      tabCnt = 0
      while True:
        if line[tabCnt] == '\t':
          tabCnt += 1
        else:
          break
      return tabCnt

  def parseImport(self, line, lineNo, fstream, indentLvl):
    imp = ImportInfo(self._curFile, line.replace('import', '').strip(self._stripChars))
    imp.setLineNo(lineNo)
    self._imports.append(imp)
    
  def parseFrom(self, line, lineNo, fstream, indentLvl):
    fromPkg, module = line.replace('from', '').split('import')
    imp = ImportInfo(self._curFile, module.strip(self._stripChars), fromPkg.strip(self._stripChars))
    imp.setLineNo(lineNo)
    self._imports.append(imp)
    
  def parseClass(self, line, lineNo, fstream, indentLvl):
    name = ''
    base = ''

    if line.find('(') != -1:
      name, base = line.replace('class', '').strip(' \t\r\n:)').split('(')
    else:
      name = line.replace('class', '').strip(' \t\r\n:)')
      
    classInfo = ClassInfo(self._curFile, name, base)
    classInfo.setLineNo(lineNo)
    self._classes[name] = classInfo
    self._curClass = name

    while True:
      line, lineNo = self.readLine(fstream)
      indentLevel = self.getIndentLevel(line)

      if indentLevel <= indentLvl:
        self._curClass = ''
        return (line.strip(self._stripChars), lineNo, fstream, indentLevel)
      else:
        stripedLine = line.strip(self._stripChars)
        self.parseLine(stripedLine, lineNo, fstream, indentLevel)
      
    self._curClass = ''
    return None
    
  def parseMethod(self, line, lineNo, fstream, indentLvl):
    name, params = line.replace('def', '').strip(' \t\r\n:)').split('(')
    
    methodInfo = MethodInfo(self._curFile, name, indentLvl)
    methodInfo.setLineNo(lineNo)
    paramlist = params.split(',')
    for param in paramlist:
      methodInfo.appendParam(param.strip(' \t\r\n:)'))
      
    self._curMethod = name
    
    if self._curClass == '':
      self._globalMethods[name] = methodInfo
    else:
      self._classes[self._curClass].appendMethod(name, methodInfo)

    while True:
      line, lineNo = self.readLine(fstream)
      indentLevel = self.getIndentLevel(line)
            
      if indentLevel <= indentLvl:
        if self._curClass != '' and indentLevel <= self._classes[self._curClass].getIndentLevel():
          self._curClass = ''
        self._curMethod = ''
        return (line.strip(self._stripChars), lineNo, fstream, indentLevel)

    self._curMethod = ''
    return None
      
##############################################################################
# test codes
if __name__ == '__main__':
  import sys
  import getopt
  
  try:
    opts, args = getopt.getopt(sys.argv[1:], "hd:v", ["help", "filename="])
  except getopt.GetoptError, err:
    print 'Get opt failed.%s%s' % (os.linesep, str(err))
    sys.exit(2)

  filename = ''
  for opt, arg in opts:
    if opt.lower() == '--filename' or opt.lower() == '--f':
      filename = os.path.realpath(arg)
      print 'Source File: %s' % filename
      print '------------------------------------'

  if filename == '':
    print 'Please enter a file name by \'--filename=\' or \'--f\' argument.'
    
  if not os.path.exists(filename):
    print 'Can not find the file: %s' % filename
    sys.exit(2)
    
  parser = PySourceParser()
  parser.parse(filename)
  
  for imp in parser.getImports():
    fromPkg = imp.getPackageName()
    module = imp.getModuleName()
    if fromPkg == '':
      print 'import %s [line %d]' % (module, imp.getLineNo())
    else:
      print 'from %s import %s [line %d]' % (fromPkg, module, imp.getLineNo())

  for cls in parser.getClasses():
    print '%sclass %s(%s) [line %d]' % ('  ' * cls.getIndentLevel(), cls.getName(), cls.getBase(), cls.getLineNo())
    for mth in cls.getMethods():
      print '%sdef %s() [line %d]' % ('  ' * mth.getIndentLevel(), mth.getName(), mth.getLineNo())

  for gVar in parser.getGlobalVariables():
    print gVar

  for gMth in parser.getGlobalMethods():
    print '%sdef %s() [line %d]' % ('  ' * gMth.getIndentLevel(), gMth.getName(), gMth.getLineNo())
